///////////////////////////////////////////////////////////////////////////////
//	FILENAME:	eTImageComponent.m
//	SUMMARY:	Implementation for a container of imageable data.
//	SUPERCLASS:	Object
//	INTERFACE:	None, but it "exports" eTImageComponentIcon.
//	PROTOCOLS:	<ComponentData,
//				ETFDSupport,HTMDSupport,LaTeXSupport>
//	AUTHOR:		Rohit Khare
//	COPYRIGHT:	(c) 1994 California Institure of Technology, eText Project
///////////////////////////////////////////////////////////////////////////////
//	IMPLEMENTATION COMMENTS
//		The concept is that eText systems only access NXImages through this
//	component, which offers default services for image coercion and encoding.
//
//		The UI has the responsibility for passing in the desired filespec.
//	Thus to keep a .pcx file as .pcx, pass in a complete path to the .pcx
//	on disk and eText will either reference it or copy it as-is into the
//	future (without filter-services, you will get eTImageComponentIcon.tiff)
//		If the file cannot be found (or isDirty), eText will generate a .tiff
//	and reset the names correctly.
//
//		Since this component maps onto an IMG tag, the only format with
//	wide acceptance for IMG is GIF; so any non-gif file will be coerced.
//	Similarly, LaTeX only has provisions for eps, so it will, too.
//
//	LaTeX and HTML representations will force the creation of local copies of
//	images (this can/will change in the future, cf HTML OODB ideas)
//
//  shared images are currently envisioned for sysBitmaps like NXLinkButton
//
//	Note on HTML calling convention: this object maps exclusively onto the
//	IMG tag, which can occur in the body of another anchor-like element.
///////////////////////////////////////////////////////////////////////////////
//	HISTORY
//	01/22/95:	Added call to handle caption strings.
//	12/23/94:	Patched to always reset etDoc on read/write
//	07/19/94:	Rearchitected
//	07/14/94:	Created. Aliases the NeXTSTEP NXImage API.
///////////////////////////////////////////////////////////////////////////////

#import "eTImageComponent.h"
#import "Kludges.subproj/EPSWriting.h"
#import "Kludges.subproj/GIFWriting.h"
#define _eTImageComponentVERSION	10

@implementation eTImageComponent
//	NXImage	*theImage;
//	BOOL	isShared;

+ newImageNamed:(const char *)newName
{
	static id	_imageComponentTable=nil;
	id newObj;
	id newImg;
	NXAtom theName;
	
	theName = NXUniqueString(newName);
	if (!_imageComponentTable) {
		_imageComponentTable = [[HashTable alloc] initKeyDesc:"%"];
	}
	newObj = [_imageComponentTable valueForKey:theName];
	if (!newObj) {
		if (newImg = [NXImage findImageNamed:theName]){	// system bitmap?
			[newImg setScalable:NO];			// this is a shared image!
			[newImg setDataRetained:YES];		// write TIFFs of sysBitmaps
			[newImg setEPSUsedOnResolutionMismatch:YES];
			// Here's a hack for dealing with recalcitrant alpha channels:
			 [newImg setBackgroundColor:NX_COLORWHITE];
		} else {
			newImg = [[NXImage alloc] init];
			if (![newImg useFromFile:theName]){
				[newImg free]; 
				newImg = [NXImage findImageNamed:"eTImageComponentIcon"];
			} else {
				// WHAT Happens to naming if theName is an absolute path?
				[newImg setName:theName];
			}
		}
		
		newObj = [[[eTImageComponent alloc] initInDoc:nil linked:NO]
			useImage:newImg name:theName path:"" shared:YES];
		[_imageComponentTable insertKey:theName value:newObj];
	}
	return newObj;
}
- theImage {return theImage;}
- setComponentName:(const char*)newComponentName
	{return (isShared ? nil : [super setComponentName:newComponentName]);}
- (BOOL)isShared {return isShared;}
- (BOOL)isMutable {return (!isShared && [super isMutable]);}
//////// begin semi-private API section ///////
- initInDoc:newDoc linked: (BOOL) linked
{
	[super initInDoc:newDoc linked:linked];
	isShared = NO; shouldEdit = YES; theImage = nil;
	return self;
}
- free
{
	if (isShared || [theImage name]) return self; // stop the free chain
	theImage = [theImage free];
	return self = [super free];
}

- useImage:(NXImage *)newImage 
	  name:(const char *) newComponentName
	  path:(const char *) newCurrentPath
	shared:(BOOL) newShared;
{
	theImage = newImage;
	componentName = NXUniqueString(newComponentName);
	currentPath = NXUniqueString(newCurrentPath);
	isShared = newShared;
	return self;
}

- readComponentFromPath:(const char *)newPath
{
	if (newPath != currentPath) 
		[super readComponentFromPath:newPath];
	
	// now we have filled in componentName/currentPath
	// go out to disk and find the image
	theImage = [[NXImage alloc] init];
	[theImage setScalable:YES];
	[theImage setDataRetained:YES];
	[theImage setEPSUsedOnResolutionMismatch:YES];
	// Here's a hack for dealing with recalcitrant alpha channels:
	[theImage setBackgroundColor:NX_COLORWHITE];
	
	if (![theImage loadFromFile:currentPath]){
		char temp[MAXPATHLEN];

		strcpy(temp, currentPath);
		strcat(temp, ".eps");
		if (![theImage loadFromFile:temp]){
			strcpy(temp, currentPath);
			strcat(temp, ".tiff");
			if (![theImage loadFromFile:temp]){
				strcpy(temp, currentPath);
				[theImage free];
				// use eTImageComponentIcon.tiff to indicate an error
				theImage = [NXImage findImageNamed:"eTImageComponentIcon"];
				isShared = YES;
			}
		}
		currentPath = NXUniqueString(temp);
	}
	return self;
}
- writeComponentToPath:(NXAtom)path inFormat:(int) theFormat
{
	const char *tmp;
	char target[MAXPATHLEN];
	
	if ((theFormat == ETFD_FMT) && (isShared || [theImage name]))
		return self;
	switch(theFormat) {
		case ETFD_FMT:
		//	if it's not (a link) or (clean and the target is found),
		//		if the (source is found and it's clean), copy
		//		else write.
			if (isShared) break;	// special case for etfds.
			sprintf(target,"%s/%s",path,componentName);
			if (!((isLinked) || ((!isDirty) && (!access(target,F_OK|R_OK))))){
				if ((!isDirty) && *currentPath &&
					(!access(currentPath, F_OK|R_OK))) {
					// copy
					char cmd[2*MAXPATHLEN];

					sprintf(cmd,"cp -rp \"%s\" \"%s\"", currentPath, target);
					system(cmd);
					currentPath = NXUniqueString(target);
				} else {
					id	repList;
					int	i,j;
					BOOL foundEPS=NO;
					NXStream *stream;

					// choose format
					repList = [theImage representationList];
					i = [repList count];
					for(j=0; ((j<i) && !foundEPS); j++){
					if ([[repList objectAt:j] isKindOf:[NXEPSImageRep class]])
						foundEPS = YES;
					}		

					stream = NXOpenMemory(NULL, 0, NX_READWRITE);
					if (foundEPS) {			
						[theImage writeEPS:stream];
						if (!rindex(target, '.') || 
							strcasecmp(".eps", rindex(target, '.')))
							strcat(target, ".eps");
					} else {
						[theImage writeTIFF:stream allRepresentations:YES];
						if (!rindex(target, '.') || 
							strncasecmp(".tif", rindex(target, '.'),3))
							strcat(target, ".tiff");
					}
					NXSaveToFile(stream, target);
					NXCloseMemory(stream, NX_FREEBUFFER);
					currentPath = NXUniqueString(target);
					componentName = NXUniqueString(rindex(target,'/')+1);
				}
				isDirty=NO;
			}
			[etDoc registerComponent:componentName];
			break;
		case HTMD_FMT:
		// Always is a gif. If it doesn't exist, copy a .gif or create one.
			if (componentName) tmp = componentName;
			else if ([theImage name]) tmp = [theImage name];
			else tmp = [[self class] name];

			sprintf(target,"%s/%s",path,tmp);
			if(!rindex(target, '.') || strcasecmp(".gif", rindex(target, '.')))
				strcat(target, ".gif");
			if (access(target,F_OK|R_OK) || isDirty) {
				// if currentPath is a gif, copy.
				if (rindex(currentPath, '.') &&
						(!strcasecmp(".gif", rindex(currentPath, '.'))) &&
						(!access(currentPath,F_OK|R_OK))) {
					char cmd[2*MAXPATHLEN];

					sprintf(cmd,"cp -rp \"%s\" \"%s\"", currentPath, target);
					system(cmd);
				} else {
					[theImage writeGIFtoPath:target];
				}
			}
			[etDoc registerComponent:(rindex(target,'/')+1)];
			break;
		case TeXD_FMT:
		// Always is an eps. If it doesn't exist, copy a .eps or create one.
			if (componentName) tmp = componentName;
			else if ([theImage name]) tmp = [theImage name];
			else tmp = [[self class] name];
			sprintf(target,"%s/%s",path,tmp);
			if (!rindex(target, '.') || strcasecmp(".eps", rindex(target, '.')))
				strcat(target, ".eps");
			if (access(target,F_OK|R_OK) || isDirty) {
				// if currentPath is an eps, copy.
				if ((rindex(currentPath, '.')) &&
						(!strcasecmp(".eps", rindex(currentPath, '.'))) &&
						(!access(currentPath,F_OK|R_OK))) {
					char cmd[2*MAXPATHLEN];

					sprintf(cmd,"cp -rp \"%s\" \"%s\"", currentPath, target);
					system(cmd);
				} else {
					NXStream *stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
					[theImage writeEPS:stream];
					NXSaveToFile(stream, target);
					NXCloseMemory(stream, NX_FREEBUFFER);
				}
			}
			[etDoc registerComponent:(rindex(target,'/')+1)];
			break;
	}
	return self;
}

- writeComponentToPboard:thePboard
{
	// I don't know why yet, but I want to defeat dragging of shared images
	return (isShared ? nil : [super writeComponentToPboard:thePboard]);
}
- addToPboard:pboard
{
	id	repList;
	int	i,j;
	BOOL foundEPS=NO;
	NXStream *stream;

	// we can let people copy them, though.???
	if (!theImage) return nil;

	// choose format
	repList = [theImage representationList];
	i = [repList count];
	for(j=0; ((j<i) && !foundEPS); j++){
	if ([[repList objectAt:j] isKindOf:[NXEPSImageRep class]])
		foundEPS = YES;
	}		

	stream = NXOpenMemory(NULL, 0, NX_READWRITE);
	if (foundEPS) {			
		[theImage writeEPS:stream];
		[pboard addTypes:&NXPostScriptPboardType num:1 owner:nil];
		[pboard writeType:NXPostScriptPboardType fromStream:stream];
	} else {
		[theImage writeTIFF:stream allRepresentations:YES];
		[pboard addTypes:&NXTIFFPboardType num:1 owner:nil];
		[pboard writeType:NXTIFFPboardType fromStream:stream];
	}
	NXCloseMemory(stream, NX_FREEBUFFER);
	return self;
}
- readComponentFromPboard:thePboard
{
	[super readComponentFromPboard:thePboard];
	// ok, so now we have a quasi-valid entry in currentPath
	// If the image can init from thePboard, go right ahead. 
	
	theImage = [[NXImage alloc] init];
	[theImage setScalable:YES];
	[theImage setDataRetained:YES];
	[theImage setEPSUsedOnResolutionMismatch:YES];
	// Here's a hack for dealing with recalcitrant alpha channels:
	 [theImage setBackgroundColor:NX_COLORWHITE];
	
	if ([NXImage canInitFromPasteboard:thePboard]) {
		// Patch for recalcitrant f*king appkit
		if([NXEPSImageRep canInitFromPasteboard:thePboard]) {
			id epsRep = [[NXEPSImageRep alloc] initFromPasteboard:thePboard];
			[theImage useRepresentation:epsRep];
		} else 
			theImage = [theImage initFromPasteboard:thePboard];
		if (!theImage){
			theImage = [NXImage findImageNamed:"eTImageComponentIcon"];
			isShared = YES;
		}
		// special case: If we paste raw image data and there's no path,
		// componentName is currently "Untitled-%d"; we need to cat an ext.
		// we should look at how this whole schmeer works/fails with RIBs.
		if (!(currentPath && *currentPath)) {
			char tmp[MAXPATHLEN];
			
			strcpy(tmp, componentName);
			if([thePboard findAvailableTypeFrom:&NXPostScriptPboardType num:1])
				strcat(tmp, ".eps");
			else if([thePboard findAvailableTypeFrom:&N3DRIBPboardType num:1])
				strcat(tmp, ".rib");
			else strcat(tmp, ".tiff");
			componentName = NXUniqueString(tmp);
		}
	} else if (currentPath && *currentPath) {
		if (![theImage loadFromFile:currentPath]){
			[theImage free]; 
			theImage = [NXImage findImageNamed:"eTImageComponentIcon"];
			isShared = YES; 
		}
	} else {
		theImage = [NXImage findImageNamed:"eTImageComponentIcon"];
		isShared = YES;
	}
	return self;
}

- readRichText:(NXStream *)stream forView:view
{
	int i;
	id temp;
	
	temp = [view etDoc];
	if (temp && [temp respondsTo:@selector(registerComponent:)])
		etDoc = temp; // Always reset if possible; bugfix for shared images, 12/23
	NXScanf(stream, "%d ", &i);
	if (i != _eTImageComponentVERSION) {
		// bad version block.
	 NXLogError("eTImageComponent found unparseable version %d at position %d",
					i, NXTell(stream));
		return nil;
	}
	NXScanf(stream, "%c ", &isShared); isShared -= 'A';
	[super readRichText:stream forView:view];
	if (isShared){
		// we have a real problem here: we need to return the "uniq"d instance!
		
		id retval = [eTImageComponent newImageNamed:componentName];
		[self free];
		return retval;
	}	
	if (!isLinked) {
		// derive a fresh currentPath. try to load.
		char buf[MAXPATHLEN];
		
		sprintf(buf, "%s/%s",[[etDoc docInfo] docPath], componentName);
		currentPath = NXUniqueString(buf);
	}
	[self readComponentFromPath:currentPath];
	return self;
}
- writeRichText:(NXStream *)stream forView:view
{
	BOOL fakeShared;
	id temp;
	
	temp = [view etDoc];
	if (temp && [temp respondsTo:@selector(registerComponent:)])
		etDoc = temp; // Always reset if possible; bugfix for shared images, 12/23
	// this fixes a subtle bug related to images we can't locate at
	// read-time and are assigned the eTImageComponentIcon by default
	// (and thus set isShared) -- on reopen, it calls +newImageNamed with
	// the old componentName
	fakeShared = isShared;
	if (([theImage name] == NXUniqueString("eTImageComponentIcon")) &&
		strcmp([theImage name], componentName))
		fakeShared = NO;
	NXPrintf(stream, "%d %c ", _eTImageComponentVERSION, fakeShared+'A');
	[super writeRichText:stream forView:view];
	return self;
}

- writeHTML:(NXStream *)stream forView:view {
	return [self writeHTML:(NXStream *)stream forView:view withCaption:NULL];}
- writeHTML:(NXStream *)stream forView:view withCaption:(NXAtom)caption
{
	const char *tmp;
	id temp;
	
	temp = [view etDoc];
	if (temp && [temp respondsTo:@selector(registerComponent:)])
		etDoc = temp; // Always reset if possible; bugfix for shared images, 12/23
	if (componentName) tmp = componentName;
	else if ([theImage name]) tmp = [theImage name];
	else tmp = [[self class] name];
	NXPrintf(stream, "<IMG SRC=\"%s%s\" ALT=\"%v\">", tmp, 
	((!rindex(tmp, '.') || strcasecmp(".gif", rindex(tmp, '.')))?".gif":""),
		(caption && *caption) ? caption : tmp);
	return self;
}


- writeLaTeX:(NXStream *)stream forView:view
{
	const char *tmp;
	id temp;
	
	temp = [view etDoc];
	if (temp && [temp respondsTo:@selector(registerComponent:)])
		etDoc = temp; // Always reset if possible; bugfix for shared images, 12/23
	if (componentName) tmp = componentName;
	else if ([theImage name]) tmp = [theImage name];
	else tmp = [[self class] name];
	NXPrintf(stream, "\n\\centerline{\\epsffile{%s%s}}\n",tmp, 
	((!rindex(tmp, '.') || strcasecmp(".eps", rindex(tmp, '.')))?".eps":""));
	return self;
}

// VERY PRIVATE API
- setImage:newImg {theImage = newImg; return self;}
- setShared:(BOOL)newState {isShared = newState; return self;}
- registerError
{
	theImage = [NXImage findImageNamed:"eTImageComponentIcon"];
	isShared = YES;
	return self;
}
@end